﻿namespace AddressBook.Business.Security
{
	#region "Using Statements"

	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Text;
	using System.Web.Security;
	using System.Configuration.Provider;
	using System.Security.Cryptography;
	using System.Web.Configuration;
	using System.Collections.Specialized;
	using System.Configuration;
	using AddressBook.Business.Model;

	#endregion

	public class AddressBookMembershipProvider : MembershipProvider
	{
		#region "Private Properties"

		private String _applicationName;
		private MachineKeySection _machineKey;
		private MembershipPasswordFormat _passwordFormat;

		#endregion

		#region "Public Properties"

		public override string ApplicationName
		{
			get { return _applicationName; }
			set { _applicationName = value; }
		}

		public override bool EnablePasswordReset
		{
			get { throw new NotImplementedException(); }
		}

		public override bool EnablePasswordRetrieval
		{
			get { throw new NotImplementedException(); }
		}

		public override int MaxInvalidPasswordAttempts
		{
			get { throw new NotImplementedException(); }
		}

		public override int MinRequiredNonAlphanumericCharacters
		{
			get { throw new NotImplementedException(); }
		}

		public override int MinRequiredPasswordLength
		{
			get { throw new NotImplementedException(); }
		}

		public override int PasswordAttemptWindow
		{
			get { throw new NotImplementedException(); }
		}

		public override MembershipPasswordFormat PasswordFormat
		{
			get { return _passwordFormat; }
		}

		public override string PasswordStrengthRegularExpression
		{
			get { throw new NotImplementedException(); }
		}

		public override bool RequiresQuestionAndAnswer
		{
			get { throw new NotImplementedException(); }
		}

		public override bool RequiresUniqueEmail
		{
			get { throw new NotImplementedException(); }
		}

		#endregion

		#region "Initialize"

		/// <summary>
		/// Initializes the Membership Provider
		/// </summary>
		/// <param name="name"></param>
		/// <param name="config"></param>
		public override void Initialize(string name, NameValueCollection config)
		{
			_applicationName = GetConfigValue(config["applicationName"], System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);

			string temp_format = config["passwordFormat"];

			if (temp_format == null)
			{
				temp_format = "Encrypted";
			}

			switch (temp_format)
			{
				case "Hashed":
					_passwordFormat = MembershipPasswordFormat.Hashed;
					break;
				case "Encrypted":
					_passwordFormat = MembershipPasswordFormat.Encrypted;
					break;
				case "Clear":
					_passwordFormat = MembershipPasswordFormat.Clear;
					break;
				default:
					throw new ProviderException("Password format not supported.");
			}


			// Get encryption and decryption key information from the configuration.
			Configuration cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
			_machineKey = (MachineKeySection)cfg.GetSection("system.web/machineKey");

			if (_machineKey.ValidationKey.Contains("AutoGenerate"))
			{
				if (PasswordFormat != MembershipPasswordFormat.Clear)
				{
					throw new ProviderException("Hashed or Encrypted passwords are not supported with auto-generated keys.");
				}
			}
		}

		/// <summary>
		/// A helper function to retrieve config values from the configuration file
		/// </summary>
		/// <param name="configValue"></param>
		/// <param name="defaultValue"></param>
		/// <returns></returns>
		private string GetConfigValue(string configValue, string defaultValue)
		{
			if (String.IsNullOrEmpty(configValue))
				return defaultValue;

			return configValue;
		}


		#endregion

		#region "Validation Functions"

		public override bool ValidateUser(string username, string password)
		{
			using (AddressBookDataContext ctx = new AddressBookDataContext(Database.AddressBook))
			{
				User user = ctx.Users.FirstOrDefault(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase));

				if (user != null)
				{
					return CheckPassword(password, user.Password);
				}
			}

			return false;
		}

		#endregion

		#region "User Maintenance Functions"

		public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
		{
			MembershipUser user;

			using (AddressBookDataContext ctx = new AddressBookDataContext(Database.AddressBook))
			{
				if (ctx.Users.Count(u => u.Email.Equals(email, StringComparison.OrdinalIgnoreCase)) > 0)
				{
					status = MembershipCreateStatus.DuplicateEmail;
				}

				if (ctx.Users.Count(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase)) > 0)
				{
					status = MembershipCreateStatus.DuplicateUserName;
				}

				User newUser = new User();

				newUser.Username = username;
				newUser.Password = EncodePassword(password);
				newUser.Email = email;
				newUser.PasswordQuestion = passwordQuestion;
				newUser.PasswordAnswer = passwordAnswer;
				newUser.IsApproved = isApproved;
				newUser.Id = new Guid();

				ctx.Users.InsertOnSubmit(newUser);
				ctx.SubmitChanges();

				user = new MembershipUser(this.Name, newUser.Username, newUser.Id, newUser.Email, newUser.PasswordQuestion, String.Empty, newUser.IsApproved, (Boolean)newUser.IsLockedOut, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, new DateTime());
				status = MembershipCreateStatus.Success;
			}

			return user;
		}

		public override bool ChangePassword(string username, string oldPassword, string newPassword)
		{
			throw new NotImplementedException();
		}

		public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
		{
			throw new NotImplementedException();
		}

		public override string ResetPassword(string username, string answer)
		{
			throw new NotImplementedException();
		}

		public override bool UnlockUser(string userName)
		{
			throw new NotImplementedException();
		}

		public override void UpdateUser(MembershipUser user)
		{
			throw new NotImplementedException();
		}

		public override bool DeleteUser(string username, bool deleteAllRelatedData)
		{
			throw new NotImplementedException();
		}

		#endregion

		#region "Find Functions"

		public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
		{
			throw new NotImplementedException();
		}

		public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
		{
			throw new NotImplementedException();
		}

		#endregion

		#region "Get Functions"

		public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
		{
			throw new NotImplementedException();
		}

		public override int GetNumberOfUsersOnline()
		{
			throw new NotImplementedException();
		}

		public override string GetPassword(string username, string answer)
		{
			throw new NotImplementedException();
		}

		public override MembershipUser GetUser(string username, bool userIsOnline)
		{
			throw new NotImplementedException();
		}

		public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
		{
			throw new NotImplementedException();
		}

		public override string GetUserNameByEmail(string email)
		{
			throw new NotImplementedException();
		}

		#endregion

		#region "Encrpytion Functions"

		//
		// CheckPassword
		//   Compares password values based on the MembershipPasswordFormat.
		//

		private bool CheckPassword(string password, string dbpassword)
		{
			string pass1 = password;
			string pass2 = dbpassword;

			switch (PasswordFormat)
			{
				case MembershipPasswordFormat.Encrypted:
					pass2 = UnEncodePassword(dbpassword);
					break;
				case MembershipPasswordFormat.Hashed:
					pass1 = EncodePassword(password);
					break;
				default:
					break;
			}

			if (pass1 == pass2)
			{
				return true;
			}

			return false;
		}


		//
		// EncodePassword
		//   Encrypts, Hashes, or leaves the password clear based on the PasswordFormat.
		//

		private string EncodePassword(string password)
		{
			string encodedPassword = password;

			switch (PasswordFormat)
			{
				case MembershipPasswordFormat.Clear:
					break;
				case MembershipPasswordFormat.Encrypted:
					encodedPassword =
						Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
					break;
				case MembershipPasswordFormat.Hashed:
					HMACSHA1 hash = new HMACSHA1();
					hash.Key = HexToByte(_machineKey.ValidationKey);
					encodedPassword =
						Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
					break;
				default:
					throw new ProviderException("Unsupported password format.");
			}

			return encodedPassword;
		}


		//
		// UnEncodePassword
		//   Decrypts or leaves the password clear based on the PasswordFormat.
		//

		private string UnEncodePassword(string encodedPassword)
		{
			string password = encodedPassword;

			switch (PasswordFormat)
			{
				case MembershipPasswordFormat.Clear:
					break;
				case MembershipPasswordFormat.Encrypted:
					password =
						Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password)));
					break;
				case MembershipPasswordFormat.Hashed:
					throw new ProviderException("Cannot unencode a hashed password.");
				default:
					throw new ProviderException("Unsupported password format.");
			}

			return password;
		}

		//
		// HexToByte
		//   Converts a hexadecimal string to a byte array. Used to convert encryption
		// key values from the configuration.
		//

		private byte[] HexToByte(string hexString)
		{
			byte[] returnBytes = new byte[hexString.Length / 2];
			for (int i = 0; i < returnBytes.Length; i++)
				returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
			return returnBytes;
		}


		#endregion
	}
}
